package productidentification;

import java.io.File;

import common.Result;
import common.ValueResult;

/**
 * This class scans the plugins/productidentification directory and registers all valid plugins
 * found.
 * 
 * @author Ryan Cheatham
 */
public class ProductIdentifierScanner
{
  /**
   * Constructs the default scanner.
   */
  public ProductIdentifierScanner()
  {}

  /**
   * Scans the plugins/productidentification directory for valid plugins, and registers them with
   * the ProductIdentifierFactory.
   * 
   * @return a result, indicating whether the scan was successful. If it was not successful, a
   *         message describing why will be supplied.
   */
  public ValueResult<Integer> scan()
  {
    int found = 0;

    File dir = new File("plugins/productidentification");
    if (!dir.isDirectory())
    {
      return new ValueResult<Integer>(false,
          "Could not perform scan - plugins/productidentification is not a valid directory!");
    }

    File[] files = dir.listFiles();
    String errors = "";
    for (File file : files)
    {
      Result result = registerFile(file);
      if (result.getStatus() == true)
        found++;
      else
        errors += "\n- " + result.getMessage();
    }

    if (found == 0)
      return new ValueResult<Integer>(false, "Scan failed - no plugins found! Errors:" + errors);
    else if (!errors.isEmpty())
      return new ValueResult<Integer>(true, "Scan succeeded with errors:\n" + errors, found);
    else
      return new ValueResult<Integer>(true, "Scan succeeded", found);
  }

  /**
   * Attempts to register the given file as a Product Identifier plugin. The file is a valid plugin
   * if it is a Java class file that contains a class that implements the
   * {@link IProductIdentifier} interface.
   * 
   * @param file
   *          The file to register as a Product Identifier plugin.
   * @return a result, indicating if the plugin was successfully registered.
   */
  private Result registerFile(File file)
  {
    Result result = validateFile(file);
    if (result.getStatus() == false)
      return result;

    String className = file.getName();
    // strip off the .class suffix of the file name to get the class name
    className = className.substring(0, className.lastIndexOf('.'));

    try
    {
      Class<?> c = Class.forName(className);
      Object o = c.newInstance();
      if (o instanceof IProductIdentifier)
      {
        IProductIdentifier identifier = (IProductIdentifier) o;
        String pluginName = identifier.getName();
        String description = identifier.getDescription();
        ProductIdentifierFactory.addProductIdentifier(className, pluginName, description);
        return new Result(true, pluginName);
      }
      else
        return new Result(false,
            "Could not register file - class did not implement the IProductIdentifier interface!");
    }
    catch (NoClassDefFoundError e)
    {
      return new Result(false, "Could not register file - class definition was not found!");
    }
    catch (ClassNotFoundException e)
    {
      return new Result(false, "Could not register file - class was not found!");
    }
    catch (InstantiationException e)
    {
      return new Result(false, "Could not register file - class could not be instantiated!");
    }
    catch (IllegalAccessException e)
    {
      return new Result(false, "Could not register file - class could not be accessed!");
    }
  }

  /**
   * Validates some basic attributes of a potential plugin file.
   * 
   * @param file
   *          The file to validate.
   * @return a result, indicating if the file is valid, and if not, a message describing why.
   */
  private Result validateFile(File file)
  {
    if (!file.isFile())
      return new Result(false, "Could not register file - 'file' was not a file!");
    if (!file.canRead())
      return new Result(false, "Could not register file - could not read file!");
    if (!file.getName().endsWith(".class"))
      return new Result(false, "Could not register file - file was not a Java class file!");
    if (file.getName().contains("$"))
      return new Result(false, "Could not register file - class was an anonymous inner class!");

    return new Result(true);
  }
}
